﻿using System;
using System.Collections.Generic;
using LoA.Shared;
using LoA.Shared.Interfaces;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace LoA.MapEngine {
	public partial class MapComponent : DrawableGameComponent, ISuspendableGameComponent, IMap {
		private static MapComponent _current = null;
		public static MapComponent Current { get { return _current; } }

		ICamera Camera;

		public MapComponent(Game game, ICamera camera)
			: base(game) {
			if (_current == null)
				_current = this;
			this.Camera = camera;
			this.SetRegion("Termisry", 0, 0);
		}

		private const int pointsX = 50;
		private const int pointsZ = 50;
		private const int sizeX = 49; //pointsX -1
		private const int sizeZ = 49; //pointsZ -1
		private const float scale = 2f;
		private string currentRegion;
		private Point currentFragment;
#if DEBUG
		private SpriteBatch spriteBatch;
		private SpriteFont spriteFont;
#endif

		private List<Model> Maps = new List<Model>(new Model[9]);

		public override void Initialize( ) {
			this.Suspended = false;

			base.Initialize( );
		}

		protected override void LoadContent( ) {
#if DEBUG
			spriteBatch = new SpriteBatch(this.GraphicsDevice);
			spriteFont = Game.Content.Load<SpriteFont>(@"Font\Arial");
#endif
			base.LoadContent( );
		}
		protected override void UnloadContent( ) {
			base.UnloadContent( );
		}

		public bool Suspended { get; private set; }
		public void Suspend( ) { this.Suspended = true; }
		public void Resume( ) { this.Suspended = false; }

		public void UpdatePosition(Vector3 position) {
			int Mx = Convert.ToInt32(Math.Floor((double)(position.X / sizeX / scale)));
			int Mz = Convert.ToInt32(Math.Floor((double)(position.Z / sizeZ / scale)));
			this.RefreshMapSet("Termisry", Mx, Mz);
		}

		public void Move(int x, int z) {
			this.SetRegion("Termisry", x, z);
		}

		public void SetRegion(string n, int x, int z) {
			string Mn = n;
			int Mx = Convert.ToInt32(Math.Floor((double)(x / sizeX / scale)));
			int Mz = Convert.ToInt32(Math.Floor((double)(z / sizeZ / scale)));

			this.RefreshMapSet(Mn, Mx, Mz);
		}

		public void RefreshMapSet(string Mn, int Mx, int Mz) {
			RefreshMap(0, Mn, Mx - 1, Mz - 1);
			RefreshMap(1, Mn, Mx - 1, Mz);
			RefreshMap(2, Mn, Mx - 1, Mz + 1);
			RefreshMap(3, Mn, Mx, Mz - 1);
			RefreshMap(4, Mn, Mx, Mz);
			RefreshMap(5, Mn, Mx, Mz + 1);
			RefreshMap(6, Mn, Mx + 1, Mz - 1);
			RefreshMap(7, Mn, Mx + 1, Mz);
			RefreshMap(8, Mn, Mx + 1, Mz + 1);
		}

		public void RefreshMap(int m, string n, int Mx, int Mz) {
			this.currentRegion = n;
			this.currentFragment.X = Mx;
			this.currentFragment.Y = Mz;
			if (Mx >= 0 && Mz >= 0)
				if (Maps[m] == null || !(Maps[m].Tag as MapInfo).Equals(n, Mx, Mz)) {
					if (Maps[m] != null) {
						Maps.Add(Maps[m]);
						if (Maps.Count > 20)
							Maps.RemoveRange(19, Maps.Count - 20);
					}
					if (!(Mx < 0 || Mz < 0)) {
						foreach (Model map in Maps) { //Bufor jest niezbędny, ładowanie mapy za każdym razem kończy się wykończeniem stosu ;)
							if (Maps[m] != null && (Maps[m].Tag as MapInfo).Equals(n, Mx, Mz)) {
								Maps[m] = map;
								return;
							}
						}
						Maps[m] = LoadMap(n, Mx, Mz);
					}
				}
		}

		private Dictionary<string, Exception> errorList = new Dictionary<string, Exception>( );
		public Model LoadMap(string n, int x, int z) {
			if (!(x < 0 || z < 0))
				try {
					string mapPath = @".\Maps\" + n + @"\map-" + x + "-" + z;
					if (System.IO.File.Exists(this.Game.Content.RootDirectory + mapPath + ".xnb"))
						return this.Game.Content.Load<Model>(mapPath);
				} catch (Exception ex) {
					if (!errorList.ContainsKey(ex.Message)) {
						System.Diagnostics.Debug.WriteLine(ex.Message);
						errorList.Add(ex.Message, ex);
					}
				}
			return null;
		}

		public bool TestPosition(Vector3 position) {
			bool result = false;
			foreach (var map in Maps) {
				if (map != null && map.Tag != null) {
					var mapInfo = map.Tag as MapInfo;
					var mapPosition = mapInfo.GetRealPosition( ) / scale;
					int count = (mapInfo.Height - 1) * (mapInfo.Width - 1);
					int length = mapInfo.HeightMap.Length - mapInfo.Height - mapInfo.Width + 1;
					float unit = count / (float)length * mapInfo.Scale;

					int p1x = (int)Math.Floor(position.X / unit);
					int p1y = (int)Math.Floor(position.Z / unit);
					int p2x = (int)Math.Ceiling(position.X / unit);
					int p2y = (int)Math.Ceiling(position.Z / unit);
					if (p1x >= mapPosition.X && p1x < mapPosition.X + sizeX && p1y >= mapPosition.Y && p1y < mapPosition.Y + sizeZ) {
						if (p2x >= mapPosition.X && p2x < mapPosition.X + sizeX && p2y >= mapPosition.Y && p2y < mapPosition.Y + sizeZ) {
							p1x = (int)Math.Floor(position.X / unit) % sizeX;
							p1y = (int)Math.Floor(position.Z / unit) % sizeZ;
							p2x = (int)Math.Ceiling(position.X / unit) % sizeX;
							p2y = (int)Math.Ceiling(position.Z / unit) % sizeZ;

							Vector3 a = new Vector3(p1x, mapInfo.HeightMap[p1y * mapInfo.Width + p1x], p1y);
							Vector3 b = new Vector3(p2x, mapInfo.HeightMap[p1y * mapInfo.Width + p2x], p1y);
							Vector3 c = new Vector3(p2x, mapInfo.HeightMap[p2y * mapInfo.Width + p2x], p2y);
							Vector3 d = new Vector3(p1x, mapInfo.HeightMap[p2y * mapInfo.Width + p1x], p2y);

							// a,b,c || a,c,d

							Plane p1 = new Plane(a, b, c);
							Vector3 v1 = -p1.Normal;
							Plane p2 = new Plane(a, c, d);
							Vector3 v2 = -p2.Normal;

							//System.Diagnostics.Debug.WriteLine(position);
							//System.Diagnostics.Debug.WriteLine(mapInfo.Position);
							//System.Diagnostics.Debug.WriteLine(v1);
							//System.Diagnostics.Debug.WriteLine(v2);
							//System.Diagnostics.Debug.WriteLine("----------------------------------");

							if (v1.Y < 0)
								v1 = -v1;
							if (v2.Y < 0)
								v2 = -v2;

							if (v1 != Vector3.Up || v2 != Vector3.Up)
								return false;

							return true;
						}
					}
					result = true;
				}
			}
			return (result ? true : false);
		}
	}
}